package cn.cleanarch.infra.idp.pms.service.impl;

import cn.cleanarch.infra.idp.commons.domain.dto.PageResult;
import cn.cleanarch.infra.idp.pms.dao.repository.DesignFileRepository;
import cn.cleanarch.infra.idp.pms.dao.repository.DesignFileTagRepository;
import cn.cleanarch.infra.idp.pms.dao.repository.DesignFileVersionRepository;
import cn.cleanarch.infra.idp.pms.domain.dto.*;
import cn.cleanarch.infra.idp.pms.domain.entity.DesignFile;
import cn.cleanarch.infra.idp.pms.domain.entity.DesignFileTag;
import cn.cleanarch.infra.idp.pms.domain.entity.DesignFileVersion;
import cn.cleanarch.infra.idp.pms.domain.query.DesignFileQuery;
import cn.cleanarch.infra.idp.pms.domain.query.DesignFileVersionQuery;
import cn.cleanarch.infra.idp.pms.service.DesignFileService;
import cn.cleanarch.infra.idp.pms.utils.SecurePreviewTokenUtil;
import cn.cleanarch.infra.idp.web.exception.Exceptions;
import cn.cleanarch.infra.idp.pms.constants.DesignFileConstants;
import cn.cleanarch.infra.idp.pms.constants.FileTypeConstants;
import cn.cleanarch.infra.idp.pms.constants.ApiPathConstants;
import cn.cleanarch.infra.idp.pms.constants.ContentTypeConstants;

import java.time.LocalDateTime;
import cn.cleanarch.infra.idp.pms.constants.StorageConstants;
import cn.cleanarch.infra.idp.pms.utils.ExtractCacheManager;
import cn.cleanarch.infra.idp.s3.domain.entity.S3Config;
import cn.cleanarch.infra.idp.s3.service.S3ConfigService;
import cn.cleanarch.infra.idp.s3.service.S3Service;
import cn.cleanarch.infra.idp.uaa.core.util.LoginSessionTool;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import jakarta.servlet.http.HttpServletResponse;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * 设计文件服务实现类
 */
@Service
public class DesignFileServiceImpl implements DesignFileService {

    private static final Logger logger = LoggerFactory.getLogger(DesignFileServiceImpl.class);

    private final DesignFileRepository designFileRepository;
    private final DesignFileTagRepository designFileTagRepository;
    private final ExtractCacheManager extractCacheManager;
    private final S3ConfigService s3ConfigService;
    private final S3Service s3Service;
    private final DesignFileVersionRepository designFileVersionRepository;

    public DesignFileServiceImpl(DesignFileRepository designFileRepository,
                                DesignFileTagRepository designFileTagRepository,
                                ExtractCacheManager extractCacheManager,
                                S3ConfigService s3ConfigService,
                                S3Service s3Service,
                                DesignFileVersionRepository designFileVersionRepository) {
        this.designFileRepository = designFileRepository;
        this.designFileTagRepository = designFileTagRepository;
        this.extractCacheManager = extractCacheManager;
        this.s3ConfigService = s3ConfigService;
        this.s3Service = s3Service;
        this.designFileVersionRepository = designFileVersionRepository;
    }

    @Override
    public PageResult<DesignFileDTO> pageQuery(DesignFileQuery query) {
        Page<DesignFile> page = new Page<>(query.getPageNo(), query.getPageSize());
        QueryWrapper<DesignFile> wrapper = new QueryWrapper<>();
        
        // 构建查询条件
        if (query.getName() != null && !query.getName().isEmpty()) {
            wrapper.like("name", query.getName());
        }
        if (query.getFileType() != null && !query.getFileType().isEmpty()) {
            wrapper.eq("file_type", query.getFileType());
        }
        if (query.getSecurityStatus() != null && !query.getSecurityStatus().isEmpty()) {
            wrapper.eq("security_status", query.getSecurityStatus());
        }
        if (query.getOwner() != null && !query.getOwner().isEmpty()) {
            wrapper.eq("owner", query.getOwner());
        }
        if (query.getProjectId() != null) {
            wrapper.eq("project_id", query.getProjectId());
        }
        if (query.getStatus() != null && !query.getStatus().isEmpty()) {
            wrapper.eq("status", query.getStatus());
        }
        
        // 添加排序
        wrapper.orderByDesc("create_time");
        
        Page<DesignFile> result = designFileRepository.page(page, wrapper);
        List<DesignFileDTO> dtoList = result.getRecords().stream()
            .map(this::convertToDTO)
            .collect(Collectors.toList());
            
        return new PageResult<DesignFileDTO>((int) result.getTotal(), dtoList);
    }
    
    @Override
    public PageResult<DesignFileVersionDTO> pageQueryVersion(DesignFileVersionQuery query) {
        Page<DesignFileVersion> page = new Page<>(query.getPage(), query.getSize());
        QueryWrapper<DesignFileVersion> wrapper = new QueryWrapper<>();
        
        // 构建查询条件
        if (query.getFileId() != null) {
            wrapper.eq("file_id", query.getFileId());
        }
        if (query.getVersionNumber() != null && !query.getVersionNumber().isEmpty()) {
            wrapper.like("version_number", query.getVersionNumber());
        }
        if (query.getDescription() != null && !query.getDescription().isEmpty()) {
            wrapper.like("description", query.getDescription());
        }
        if (query.getFileType() != null && !query.getFileType().isEmpty()) {
            wrapper.eq("file_type", query.getFileType());
        }
        if (query.getOperationType() != null && !query.getOperationType().isEmpty()) {
            wrapper.eq("operation_type", query.getOperationType());
        }
        if (query.getVersionStatus() != null && !query.getVersionStatus().isEmpty()) {
            wrapper.eq("version_status", query.getVersionStatus());
        }
        if (query.getOperatorId() != null) {
            wrapper.eq("operator_id", query.getOperatorId());
        }
        
        // 添加排序
        wrapper.orderByDesc("create_time");
        
        Page<DesignFileVersion> result = designFileVersionRepository.page(page, wrapper);
        List<DesignFileVersionDTO> dtoList = result.getRecords().stream()
            .map(this::convertToDTO)
            .collect(Collectors.toList());
            
        return new PageResult<DesignFileVersionDTO>((int) result.getTotal(), dtoList);
    }

    @Override
    public DesignFile getById(Long id) {
        return designFileRepository.getDetailById(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public DesignFile create(DesignFile file) {
        // 设置默认值
        file.setVersion(DesignFileConstants.DEFAULT_VERSION);
        file.setStatus(DesignFileConstants.STATUS_NORMAL);
        file.setUsedStatus(DesignFileConstants.USED_STATUS_UNUSED);
        file.setSecurityStatus(DesignFileConstants.SECURITY_STATUS_UNSCANNED);
        LocalDateTime currentTime = LocalDateTime.now();
        file.setCreateTime(currentTime);
        file.setUpdateTime(currentTime);
        
        // 保存文件
        designFileRepository.save(file);
        
        // 保存标签关联
        if (file.getTags() != null && !file.getTags().isEmpty()) {
            designFileRepository.saveTagRelations(file.getId(), file.getTags());
        }
        
        return file;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public DesignFile update(DesignFile file) {
        DesignFile existingFile = designFileRepository.getById(file.getId());
        if (existingFile == null) {
            Exceptions.throwException("文件不存在");
        }
        
        // 更新标签关联
        designFileRepository.deleteTagRelationsByFileId(file.getId());
        if (file.getTags() != null && !file.getTags().isEmpty()) {
            designFileRepository.saveTagRelations(file.getId(), file.getTags());
        }
        
        // 使用MyBatis-Plus的update方法，只更新非空字段
        com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<DesignFile> updateWrapper = 
            new com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper<>();
        updateWrapper.eq("id", file.getId());
        
        // 只更新非null字段
        if (file.getName() != null) updateWrapper.set("name", file.getName());
        if (file.getDescription() != null) updateWrapper.set("description", file.getDescription());
        if (file.getFileType() != null) updateWrapper.set("file_type", file.getFileType());
        if (file.getFileSize() != null) updateWrapper.set("file_size", file.getFileSize());
        if (file.getFileKey() != null) updateWrapper.set("file_key", file.getFileKey());
        if (file.getFileMd5() != null) updateWrapper.set("file_md5", file.getFileMd5());
        if (file.getVersion() != null) updateWrapper.set("version", file.getVersion());
        if (file.getStatus() != null) updateWrapper.set("status", file.getStatus());
        if (file.getOwnerId() != null) updateWrapper.set("owner_id", file.getOwnerId());
        if (file.getUsedStatus() != null) updateWrapper.set("used_status", file.getUsedStatus());
        if (file.getSecurityStatus() != null) updateWrapper.set("security_status", file.getSecurityStatus());
        if (file.getLastAccessTime() != null) updateWrapper.set("last_access_time", file.getLastAccessTime());
        if (file.getDeletedAt() != null) updateWrapper.set("deleted_at", file.getDeletedAt());
        if (file.getStorageStatus() != null) updateWrapper.set("storage_status", file.getStorageStatus());
        if (file.getProjectId() != null) updateWrapper.set("project_id", file.getProjectId());
        
        // 更新时间为当前时间
        updateWrapper.set("update_time", LocalDateTime.now());
        
        designFileRepository.update(updateWrapper);
        
        // 创建更新操作的版本记录
        createUpdateVersion(file);
        
        // 返回更新后的文件信息
        return designFileRepository.getById(file.getId());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(Long id) {
        DesignFile file = designFileRepository.getById(id);
        if (file == null) {
            Exceptions.throwException(ApiPathConstants.ERROR_FILE_NOT_FOUND);
        }
        
        // 软删除
        file.setStatus(DesignFileConstants.STATUS_DELETED);
        file.setUpdateTime(LocalDateTime.now());
        
        designFileRepository.updateById(file);
    }

    @Override
    public List<DesignFileTagDTO> getTags() {
        QueryWrapper<DesignFileTag> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc("create_time");
        
        return designFileTagRepository.list(queryWrapper).stream()
            .map(this::convertToDTO)
            .collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public DesignFileTagDTO createTag(DesignFileTagDTO tagDTO) {
        DesignFileTag tag = convertToEntity(tagDTO);
        LocalDateTime currentTime = LocalDateTime.now();
        tag.setCreateTime(currentTime);
        tag.setUpdateTime(currentTime);
        // 设置创建者ID
        tag.setCreatedBy(LoginSessionTool.getCurrentUser().getUserId());
        
        designFileTagRepository.save(tag);
        return convertToDTO(tag);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public DesignFileTagDTO updateTag(DesignFileTagDTO tagDTO) {
        DesignFileTag tag = designFileTagRepository.getById(tagDTO.getId());
        if (tag == null) {
            Exceptions.throwException(ApiPathConstants.ERROR_TAG_NOT_FOUND);
        }
        
        tag.setName(tagDTO.getName());
        tag.setColor(tagDTO.getColor());
        // 设置更新者ID和更新时间
        tag.setUpdatedBy(LoginSessionTool.getCurrentUser().getUserId());
        tag.setUpdateTime(LocalDateTime.now());
        
        designFileTagRepository.updateById(tag);
        return convertToDTO(tag);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteTag(Long id) {
        designFileTagRepository.removeById(id);
    }

    @Override
    public List<FileTypeOptionDTO> getFileTypeOptions() {
        return Arrays.asList(
            createFileTypeOption(FileTypeConstants.AXURE_RP_NAME, FileTypeConstants.AXURE_RP),
            createFileTypeOption(FileTypeConstants.HTML_ZIP_NAME, FileTypeConstants.HTML_ZIP),
            createFileTypeOption(FileTypeConstants.FIGMA_NAME, FileTypeConstants.FIGMA),
            createFileTypeOption(FileTypeConstants.SKETCH_NAME, FileTypeConstants.SKETCH),
            createFileTypeOption(FileTypeConstants.ADOBE_XD_NAME, FileTypeConstants.ADOBE_XD),
            createFileTypeOption(FileTypeConstants.IMAGE_NAME, FileTypeConstants.IMAGE),
            createFileTypeOption(FileTypeConstants.OTHER_NAME, FileTypeConstants.OTHER)
        );
    }

    /**
     * 创建FileTypeOptionDTO实例
     */
    private FileTypeOptionDTO createFileTypeOption(String name, String value) {
        FileTypeOptionDTO dto = new FileTypeOptionDTO();
        dto.setName(name);
        dto.setValue(value);
        return dto;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void scanFileSecurity(Long id) {
        DesignFile file = designFileRepository.getById(id);
        if (file == null) {
            Exceptions.throwException(ApiPathConstants.ERROR_FILE_NOT_FOUND);
        }
        
        // 模拟安全扫描(这里可以集成真实的安全扫描工具)
        boolean isSafe = Math.random() > 0.2; // 80%概率是安全的
        
        file.setSecurityStatus(isSafe ? DesignFileConstants.SECURITY_STATUS_SAFE : DesignFileConstants.SECURITY_STATUS_UNSAFE);
        file.setUpdateTime(LocalDateTime.now());
        
        designFileRepository.updateById(file);
    }

        @Override
    public DesignFileDTO convertToDTO(DesignFile entity) {
        if (entity == null) {
            return null;
        }

        DesignFileDTO dto = new DesignFileDTO();
        dto.setId(entity.getId());
        dto.setName(entity.getName());
        dto.setDescription(entity.getDescription());
        dto.setFileType(entity.getFileType());
        dto.setFileSize(entity.getFileSize());
        dto.setFileKey(entity.getFileKey());
        dto.setFileMd5(entity.getFileMd5());
        dto.setVersion(entity.getVersion());
        dto.setStatus(entity.getStatus());
        dto.setUsedStatus(entity.getUsedStatus());
        dto.setSecurityStatus(entity.getSecurityStatus());
        dto.setOwnerId(entity.getOwnerId());
        dto.setProjectId(entity.getProjectId());
        dto.setCreatedBy(entity.getCreatedBy());
        dto.setUpdatedBy(entity.getUpdatedBy());
        dto.setCreateTime(entity.getCreateTime());
        dto.setUpdateTime(entity.getUpdateTime());
        dto.setCurrentVersionId(entity.getCurrentVersionId());

        return dto;
    }
    
    private DesignFileTagDTO convertToDTO(DesignFileTag entity) {
        if (entity == null) {
            return null;
        }
        
        DesignFileTagDTO dto = new DesignFileTagDTO();
        dto.setId(entity.getId());
        dto.setName(entity.getName());
        dto.setColor(entity.getColor());
        dto.setCreatedBy(entity.getCreatedBy());
        dto.setUpdatedBy(entity.getUpdatedBy());
        // 直接设置LocalDateTime类型的时间字段
        dto.setCreatedAt(entity.getCreateTime());
        dto.setUpdatedAt(entity.getUpdateTime());
        
        return dto;
    }
    
    private DesignFileTag convertToEntity(DesignFileTagDTO dto) {
        if (dto == null) {
            return null;
        }
        
        DesignFileTag entity = new DesignFileTag();
        entity.setId(dto.getId());
        entity.setName(dto.getName());
        entity.setColor(dto.getColor());
        entity.setCreatedBy(dto.getCreatedBy());
        entity.setUpdatedBy(dto.getUpdatedBy());
        // 直接设置LocalDateTime类型的时间字段
        entity.setCreateTime(dto.getCreatedAt());
        entity.setUpdateTime(dto.getUpdatedAt());
        
        return entity;
    }

    @Override
    public String getFileDownloadUrl(Long fileId) {
        DesignFile file = designFileRepository.getById(fileId);
        if (file == null) {
            Exceptions.throwException(ApiPathConstants.ERROR_FILE_NOT_FOUND);
        }

        // 基于当前版本ID生成下载URL
        Long currentVersionId = file.getCurrentVersionId();
        if (currentVersionId == null) {
            Exceptions.throwException("文件没有当前版本信息，无法生成下载");
        }

        logger.info("基于版本生成下载URL - 文件ID: {}, 当前版本ID: {}", fileId, currentVersionId);
        return getVersionDownloadUrl(currentVersionId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<DesignFileDTO> uploadFilesBatch(MultipartFile[] files) {
        if (files == null || files.length == 0) {
            Exceptions.throwException(ApiPathConstants.ERROR_FILE_LIST_EMPTY);
        }
        
        if (files.length > StorageConstants.MAX_FILES_PER_BATCH) {
            throw new IllegalArgumentException("批量上传文件数量不能超过: " + StorageConstants.MAX_FILES_PER_BATCH);
        }
        
        return Arrays.stream(files)
            .map(file -> {
                try {
                    return uploadFileOnly(file);
                } catch (Exception e) {
                    // 记录错误但继续处理其他文件
                    System.err.println("文件上传失败: " + file.getOriginalFilename() + " - " + e.getMessage());
                    return null;
                }
            })
            .filter(result -> result != null)
            .collect(Collectors.toList());
    }

    /**
     * 根据文件名获取文件类型
     */
    private String getFileType(String fileName) {
        return FileTypeConstants.getFileTypeByExtension(fileName);
    }

    /**
     * 获取预览类型
     */
    private String getPreviewType(String fileType) {
        switch (fileType) {
            case FileTypeConstants.HTML_ZIP:
                return DesignFileConstants.PREVIEW_TYPE_HTML;
            case FileTypeConstants.IMAGE:
            case FileTypeConstants.JPEG:
            case FileTypeConstants.PNG:
            case FileTypeConstants.GIF:
            case FileTypeConstants.SVG:
            case FileTypeConstants.WEBP:
            case FileTypeConstants.BMP:
            case FileTypeConstants.TIFF:
            case FileTypeConstants.ICO:
                return DesignFileConstants.PREVIEW_TYPE_IMAGE;
            case FileTypeConstants.AXURE_RP:
            case FileTypeConstants.FIGMA:
            case FileTypeConstants.SKETCH:
            case FileTypeConstants.ADOBE_XD:
            case FileTypeConstants.PHOTOSHOP:
            case FileTypeConstants.ILLUSTRATOR:
            case FileTypeConstants.PDF:
            case FileTypeConstants.DOC:
            case FileTypeConstants.DOCX:
            case FileTypeConstants.XLS:
            case FileTypeConstants.XLSX:
            case FileTypeConstants.PPT:
            case FileTypeConstants.PPTX:
                return DesignFileConstants.PREVIEW_TYPE_DOCUMENT;
            default:
                return DesignFileConstants.PREVIEW_TYPE_DOWNLOAD;
        }
    }

    /**
     * 统一的ZIP文件解压方法
     * @param versionFile 设计文件对象（预览时使用）
     * @param extractPath 解压路径
     * @return 解压是否成功
     */
    private boolean unifiedExtractZipFile(DesignFileVersion versionFile, String extractPath) {
        Long versionId = versionFile.getId();
        logger.info("开始统一解压ZIP文件 - 版本文件ID: {}, 解压路径: {}", versionId, extractPath);
        try {
            // 使用锁防止并发解压
            ReentrantLock lock = extractCacheManager.getExtractLock(versionId);
            boolean acquired = lock.tryLock(DesignFileConstants.EXTRACT_LOCK_TIMEOUT, TimeUnit.SECONDS);
            
            if (!acquired) {
                logger.error("获取解压锁超时 - 版本文件ID: {}", versionId);
                return false;
            }
            
            try {
                logger.info("获取解压锁成功 - 版本文件ID: {}", versionId);
                
                // 检查是否已经解压过
                boolean isAlreadyExtracted = extractCacheManager.isExtracted(versionId);
                logger.info("解压状态检查 - 版本文件ID: {}, 是否已解压: {}", versionId, isAlreadyExtracted);
                
                if (isAlreadyExtracted) {
                    logger.info("文件已存在于解压路径，跳过解压 - 文件ID: {}, 解压路径: {}", versionId, extractPath);
                    return true;
                }
                
                // 创建解压目录
                Path extractDir = Paths.get(extractPath);
                Files.createDirectories(extractDir);
                logger.info("创建解压目录完成 - 路径: {}", extractDir);
                
                // 首先检查ZIP文件中是否有且仅有一个index.html文件
                byte[] fileContent = null;

                // 从S3获取真实文件内容
                logger.info("开始从S3获取文件内容 - 文件ID: {}, 文件键: {}", versionId, versionFile.getFileKey());
                fileContent = getFileContentFromS3(versionFile);
                logger.info("从S3获取文件内容完成 - 文件ID: {}, 文件大小: {} 字节", versionId, fileContent.length);
                
                // 检查ZIP中是否有且仅有一个index.html
                String indexHtmlDir = null;
                try (ZipInputStream zipInputStream = new ZipInputStream(new java.io.ByteArrayInputStream(fileContent))) {
                    ZipEntry entry;
                    java.util.List<String> indexHtmlPaths = new java.util.ArrayList<>();
                    
                    while ((entry = zipInputStream.getNextEntry()) != null) {
                        String entryName = entry.getName().toLowerCase();
                        // 检查是否包含index.html文件
                        if (entryName.endsWith("index.html")) {
                            indexHtmlPaths.add(entry.getName()); // 保存原始路径
                        }
                        zipInputStream.closeEntry();
                    }
                    
                    if (indexHtmlPaths.isEmpty()) {
                        logger.error("ZIP文件不包含index.html文件");
                        return false;
                    } else if (indexHtmlPaths.size() > 1) {
                        logger.error("ZIP文件包含多个index.html文件: {}", String.join(", ", indexHtmlPaths));
                        return false;
                    }
                    
                    // 获取index.html所在目录
                    String indexHtmlPath = indexHtmlPaths.get(0);
                    indexHtmlDir = getIndexHtmlDirectory(indexHtmlPath);
                    logger.info("index.html路径: {}, index.html所在目录: {}", indexHtmlPath, indexHtmlDir);
                }
                
                // 现在开始解压，只解压index.html所在目录及其子目录的内容
                int entryCount = 0;
                int fileCount = 0;  // 实际解压的文件数量（不包括目录）
                java.util.List<String> extractedFiles = new java.util.ArrayList<>();  // 记录解压的文件列表
                try (ZipInputStream zipInputStream = new ZipInputStream(new java.io.ByteArrayInputStream(fileContent))) {
                    ZipEntry entry;
                    while ((entry = zipInputStream.getNextEntry()) != null) {
                        String entryName = entry.getName();
                        entryCount++;
                        
                        // 检查当前文件是否在index.html目录或其子目录下
                        if (!isInDirectoryOrSubdirectory(entryName, indexHtmlDir)) {
                            logger.debug("跳过文件，不在index.html目录或其子目录下: {} (index.html目录: {})", entryName, indexHtmlDir);
                            zipInputStream.closeEntry();
                            continue;
                        }
                        
                        // 计算相对于index.html所在目录的路径
                        String relativePath = getRelativePath(entryName, indexHtmlDir);
                        
                        Path entryPath = extractDir.resolve(relativePath);
                        
                        logger.debug("处理ZIP条目 - 原始路径: {}, 相对路径: {}, 是否为目录: {}", entryName, relativePath, entry.isDirectory());
                        
                        if (entry.isDirectory()) {
                            Files.createDirectories(entryPath);
                            logger.debug("创建目录 - 路径: {}", entryPath);
                        } else {
                            // 确保父目录存在
                            Path parentDir = entryPath.getParent();
                            if (parentDir != null) {
                                Files.createDirectories(parentDir);
                                logger.debug("创建父目录 - 路径: {}", parentDir);
                            }
                            
                            // 写入文件内容
                            logger.debug("写入文件内容 - 路径: {}", entryPath);
                            Files.copy(zipInputStream, entryPath);
                            logger.debug("文件写入完成 - 路径: {}", entryPath);
                            
                            // 记录实际解压的文件
                            fileCount++;
                            extractedFiles.add(relativePath);
                        }
                        
                        zipInputStream.closeEntry();
                    }
                }
                logger.info("解压ZIP文件完成 - 文件ID: {}, 总处理条目数: {}, 实际解压文件数: {}", versionId, entryCount, fileCount);
                logger.info("解压的文件列表: {}", String.join(", ", extractedFiles));
                
                return true;
            } finally {
                logger.info("释放解压锁 - 文件ID: {}", versionId);
                lock.unlock();
            }
        } catch (Exception e) {
            logger.error("统一解压ZIP文件失败 - 文件ID: {}, 错误信息: {}", versionId, e.getMessage(), e);
            return false;
        }
    }

    /**
     * 检查文件路径是否在指定目录或其子目录下
     * @param filePath 文件路径
     * @param dirPath 目录路径
     * @return 如果文件在目录或其子目录下返回true
     */
    private boolean isInDirectoryOrSubdirectory(String filePath, String dirPath) {
        // 如果dirPath为空，表示index.html在根目录
        if (dirPath == null || dirPath.isEmpty()) {
            // 检查是否是根目录下的文件
            return !filePath.contains("/");
        }
        
        // 确保dirPath以/结尾
        String normalizedDirPath = dirPath.endsWith("/") ? dirPath : dirPath + "/";
        
        // 检查文件路径是否以指定目录路径开头
        if (filePath.startsWith(normalizedDirPath)) {
            return true;
        }
        
        // 特殊处理：如果filePath正好等于dirPath（即目录本身）
        return filePath.equals(dirPath);
    }

    /**
     * 获取相对于指定目录的路径
     * @param filePath 原始文件路径
     * @param dirPath 目录路径
     * @return 相对路径
     */
    private String getRelativePath(String filePath, String dirPath) {
        // 如果dirPath为空，表示index.html在根目录
        if (dirPath == null || dirPath.isEmpty()) {
            return filePath;
        }
        
        // 确保dirPath以/结尾
        String normalizedDirPath = dirPath.endsWith("/") ? dirPath : dirPath + "/";
        
        // 如果filePath以dirPath开头，返回相对路径
        if (filePath.startsWith(normalizedDirPath)) {
            return filePath.substring(normalizedDirPath.length());
        }
        
        // 如果filePath等于dirPath（即目录本身），返回空字符串
        if (filePath.equals(dirPath)) {
            return "";
        }
        
        // 否则返回原路径（应该不会到达这里，因为isInDirectoryOrSubdirectory已经检查过）
        return filePath;
    }

    /**
     * 从S3获取真实文件内容
     * 从指定的S3配置下载设计文件
     */
    private byte[] getFileContentFromS3(DesignFileVersion file) throws Exception {
        logger.info("开始从S3获取文件内容 - 文件ID: {} 文件键: {}", file.getId(), file.getFileKey());
        
        try {
            // 根据设计文件的fileKey获取对应的S3配置
            // 假设fileKey包含S3配置信息或者我们通过业务类型获取默认配置
            logger.info("开始获取S3配置 - 文件ID: {}", file.getId());
            S3Config config = getS3ConfigForFile();
            if (config == null) {
                logger.warn("未找到S3配置，回退到模拟内容 - 文件ID: {}", file.getId());
                // 如果没有找到S3配置，回退到模拟内容
                return createEnhancedHtmlContent().getBytes();
            }

            logger.info("获取S3配置成功 - 配置ID: {}, 端点: {}, 存储桶: {}, 业务类型: {}", 
                       config.getId(), config.getEndpoint(), config.getBucket(), config.getBusinessType());

            // 构建S3对象键
            String objectKey = file.getFileKey();
            logger.info("构建S3对象键 - 文件ID: {}, 对象键: {}", file.getId(), objectKey);
            
            // 从S3下载文件
            logger.info("开始从S3下载文件 - 配置ID: {}, 对象键: {}", config.getId(), objectKey);
            byte[] fileContent = s3Service.downloadFileAsBytes(config, objectKey);
            logger.info("从S3下载文件完成 - 配置ID: {}, 对象键: {}, 文件大小: {} 字节", 
                       config.getId(), objectKey, fileContent.length);
            
            return fileContent;
        } catch (Exception e) {
            // 如果S3下载失败，记录错误并回退到模拟内容
            logger.error("从S3下载文件失败，回退到模拟内容 - 文件ID: {}, 错误信息: {}", file.getId(), e.getMessage(), e);
            return createEnhancedHtmlContent().getBytes();
        }
    }

    /**
     * 获取设计文件对应的S3配置
     */
    private S3Config getS3ConfigForFile() {
        
        // 方式1: 根据业务类型获取对应的S3配置
        // 数据库中存储的业务类型是大写 "DESIGN_FILE"
        logger.info("尝试根据业务类型获取S3配置 - 业务类型: DESIGN_FILE");
        S3Config config = s3ConfigService.getByBusinessType(StorageConstants.BUSINESS_TYPE_DESIGN_FILE);
        if (config != null) {
            logger.info("根据业务类型找到S3配置 - 配置ID: {}, 端点: {}", config.getId(), config.getEndpoint());
            return config;
        }
        
        logger.info("未找到业务类型为DESIGN_FILE的S3配置，尝试获取所有启用的配置");
        
        // 方式2: 获取所有启用的S3配置中的第一个
        List<S3Config> configs = s3ConfigService.getAllEnabledConfigs();
        logger.info("获取到启用的S3配置数量: {}", configs.size());
        
        if (configs.isEmpty()) {
            logger.warn("未找到任何启用的S3配置");
            return null;
        }
        
        S3Config firstConfig = configs.get(0);
        logger.info("使用第一个启用的S3配置 - 配置ID: {}, 端点: {}", firstConfig.getId(), firstConfig.getEndpoint());
        return firstConfig;
    }

    /**
     * 创建HTML ZIP文件内容
     * @param htmlContent HTML内容
     * @return ZIP文件的字节数组
     */
    private byte[] createHtmlZipFromContent(String htmlContent) throws Exception {
        logger.info("开始创建HTML ZIP文件");
        
        java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
        try (java.util.zip.ZipOutputStream zipOut = new java.util.zip.ZipOutputStream(baos)) {
            // 添加index.html文件
            zipOut.putNextEntry(new java.util.zip.ZipEntry("index.html"));
            zipOut.write(htmlContent.getBytes("UTF-8"));
            zipOut.closeEntry();
            
            // 添加基本的CSS文件
            String cssContent = "body { font-family: Arial, sans-serif; margin: 40px; background: #f8f9fa; }\n" +
                               ".container { max-width: 800px; margin: 0 auto; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }\n" +
                               ".header { border-bottom: 1px solid #e9ecef; padding-bottom: 20px; margin-bottom: 30px; }\n" +
                               ".section { margin: 30px 0; padding: 20px; border: 1px solid #e9ecef; border-radius: 6px; }\n" +
                               ".btn { display: inline-block; padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; margin: 5px; }\n" +
                               ".btn:hover { background: #0056b3; }\n" +
                               ".image-placeholder { width: 200px; height: 150px; background: #f8f9fa; border: 2px dashed #dee2e6; display: inline-block; margin: 10px; text-align: center; line-height: 150px; color: #6c757d; }\n";
            zipOut.putNextEntry(new java.util.zip.ZipEntry("css/style.css"));
            zipOut.write(cssContent.getBytes("UTF-8"));
            zipOut.closeEntry();
            
            // 添加基本的JS文件
            String jsContent = "function testJavaScript() {\n" +
                              "    alert('JavaScript功能正常！静态资源映射成功。');\n" +
                              "}\n" +
                              "\n" +
                              "function loadMoreContent() {\n" +
                              "    const content = document.getElementById('dynamic-content');\n" +
                              "    content.innerHTML = '<h4>动态加载的内容</h4><p>这是通过JavaScript动态生成的内容。</p>';\n" +
                              "    content.style.display = 'block';\n" +
                              "}\n" +
                              "\n" +
                              "document.addEventListener('DOMContentLoaded', function() {\n" +
                              "    console.log('页面加载完成，静态资源映射测试就绪');\n" +
                              "});\n";
            zipOut.putNextEntry(new java.util.zip.ZipEntry("js/main.js"));
            zipOut.write(jsContent.getBytes("UTF-8"));
            zipOut.closeEntry();
            
            // 添加一个示例图片文件（简单的文本文件作为占位符）
            zipOut.putNextEntry(new java.util.zip.ZipEntry("images/placeholder.png"));
            zipOut.write("This is a placeholder for image file".getBytes("UTF-8"));
            zipOut.closeEntry();
            
            // 完成ZIP输出流
            zipOut.finish();
        }
        
        byte[] zipBytes = baos.toByteArray();
        logger.info("HTML ZIP文件创建完成，大小: {} 字节", zipBytes.length);
        return zipBytes;
    }

    /**
     * 创建增强的HTML内容（包含静态资源引用）
     * 这个方法模拟一个完整的HTML项目结构
     */
    private String createEnhancedHtmlContent() {
        return "文件不存在!!!!!";
    }

    /**
     * 格式化文件大小显示
     */
    private String formatFileSize(long sizeInBytes) {
        if (sizeInBytes < 1024) {
            return sizeInBytes + " B";
        } else if (sizeInBytes < 1024 * 1024) {
            return String.format("%.1f KB", sizeInBytes / 1024.0);
        } else if (sizeInBytes < 1024 * 1024 * 1024) {
            return String.format("%.1f MB", sizeInBytes / (1024.0 * 1024.0));
        } else {
            return String.format("%.1f GB", sizeInBytes / (1024.0 * 1024.0 * 1024.0));
        }
    }

    /**
     * 提供静态文件服务 - 支持子路径资源访问
     */
    private void serveStaticFile(String extractPath, HttpServletResponse response) throws IOException {
        serveStaticFileWithSubPath(extractPath, extractPath, response);
    }

    /**
     * 提供静态文件服务 - 支持子路径资源访问（修复预览功能）
     * @param extractPath 解压的基础路径
     * @param requestFilePath 实际请求的文件路径
     * @param response 响应对象
     */
    private void serveStaticFileWithSubPath(String extractPath, String requestFilePath, HttpServletResponse response) throws IOException {
        logger.info("开始提供静态文件服务 - 解压路径: {}, 请求路径: {}", extractPath, requestFilePath);
        
        // 如果是根路径访问，优先返回index.html
        if (requestFilePath.equals(extractPath) || requestFilePath.endsWith("/")) {
            logger.info("处理根路径访问，尝试返回index.html");
            File indexFile = new File(new File(requestFilePath), StorageConstants.INDEX_HTML_FILE);
            logger.info("检查index.html文件 - 路径: {}, 是否存在: {}, 是否为文件: {}", 
                       indexFile.getAbsolutePath(), indexFile.exists(), indexFile.isFile());
            
            if (indexFile.exists() && indexFile.isFile()) {
                logger.info("找到index.html文件，开始提供服务");
                serveFile(indexFile, response);
                return;
            } else {
                logger.info("未找到index.html文件 - 路径: {}", indexFile.getAbsolutePath());
            }
        }
        
        // 构造完整的文件路径
        Path fullPath;
        if (requestFilePath.startsWith(extractPath)) {
            // 如果请求路径包含基础路径，直接使用
            fullPath = Paths.get(requestFilePath);
            logger.info("请求路径包含基础路径，直接使用 - 完整路径: {}", fullPath);
        } else {
            // 如果请求路径不包含基础路径，组合路径
            fullPath = Paths.get(extractPath, requestFilePath);
            logger.info("请求路径不包含基础路径，组合路径 - 完整路径: {}", fullPath);
        }
        
        logger.info("检查文件是否存在 - 路径: {}, 是否存在: {}", fullPath, Files.exists(fullPath));
        
        // 检查文件是否存在
        if (!Files.exists(fullPath)) {
            logger.warn("文件不存在 - 路径: {}", fullPath);
            
            // 如果是根路径，尝试index.html
            if (requestFilePath.equals(extractPath) || requestFilePath.endsWith("/")) {
                logger.info("再次尝试根路径下的index.html");
                File indexFile = new File(extractPath, StorageConstants.INDEX_HTML_FILE);
                logger.info("检查根路径下的index.html - 路径: {}, 是否存在: {}, 是否为文件: {}", 
                           indexFile.getAbsolutePath(), indexFile.exists(), indexFile.isFile());
                
                if (indexFile.exists() && indexFile.isFile()) {
                    logger.info("找到根路径下的index.html文件，开始提供服务");
                    serveFile(indexFile, response);
                    return;
                } else {
                    logger.info("根路径下也未找到index.html文件 - 路径: {}", indexFile.getAbsolutePath());
                }
            }
            
            logger.error("资源文件不存在: {}", requestFilePath);
            response.setStatus(StorageConstants.HTTP_STATUS_NOT_FOUND);
            response.setContentType(ContentTypeConstants.TEXT_PLAIN);
            response.getWriter().write("资源文件不存在: " + requestFilePath);
            return;
        }
        
        File file = fullPath.toFile();
        logger.info("文件信息 - 路径: {}, 是否为目录: {}, 大小: {} 字节", 
                   file.getAbsolutePath(), file.isDirectory(), file.length());
        
        if (file.isDirectory()) {
            logger.info("请求的是目录，尝试查找index.html");
            // 如果是目录，尝试找到index.html
            File indexFile = new File(file, StorageConstants.INDEX_HTML_FILE);
            logger.info("检查目录下的index.html - 路径: {}, 是否存在: {}, 是否为文件: {}", 
                       indexFile.getAbsolutePath(), indexFile.exists(), indexFile.isFile());
            
            if (indexFile.exists() && indexFile.isFile()) {
                logger.info("找到目录下的index.html文件，开始提供服务");
                serveFile(indexFile, response);
            } else {
                logger.info("目录下未找到index.html文件，列出目录内容");
                // 如果没有index.html，列出目录内容
                String[] files = file.list();
                logger.info("目录中的文件数量: {}", files != null ? files.length : 0);
                
                StringBuilder dirList = new StringBuilder();
                dirList.append("<html><head><title>Directory Listing</title></head><body>");
                dirList.append("<h1>Directory: ").append(file.getName()).append("</h1>");
                dirList.append("<ul>");
                if (files != null) {
                    for (String f : files) {
                        dirList.append("<li><a href=\"").append(f).append("\">\"").append(f).append("\"</a></li>");
                    }
                }
                dirList.append("</ul></body></html>");
                
                response.setContentType(ContentTypeConstants.TEXT_HTML_UTF8);
                response.getWriter().write(dirList.toString());
                logger.info("目录列表已返回");
            }
        } else {
            logger.info("请求的是文件，开始提供文件内容");
            // 提供文件内容
            serveFile(file, response);
        }
        
        logger.info("静态文件服务完成 - 请求路径: {}", requestFilePath);
    }

    /**
     * 提供单个文件内容 - 支持静态资源访问
     */
    private void serveFile(File file, HttpServletResponse response) throws IOException {
        logger.info("开始提供单个文件内容 - 文件路径: {}, 文件大小: {} 字节", 
                   file.getAbsolutePath(), file.length());
        
        if (!file.exists() || !file.isFile()) {
            logger.error("文件不存在或不是文件 - 路径: {}, 是否存在: {}, 是否为文件: {}", 
                        file.getAbsolutePath(), file.exists(), file.isFile());
            response.setStatus(StorageConstants.HTTP_STATUS_NOT_FOUND);
            response.setContentType(ContentTypeConstants.TEXT_PLAIN);
            response.getWriter().write(ApiPathConstants.ERROR_FILE_NOT_FOUND);
            return;
        }
        
        // 检查文件大小
        if (file.length() > StorageConstants.MAX_EXTRACTED_SIZE) {
            logger.error("文件过大 - 文件路径: {}, 文件大小: {} 字节, 最大允许大小: {} 字节", 
                        file.getAbsolutePath(), file.length(), StorageConstants.MAX_EXTRACTED_SIZE);
            response.setStatus(StorageConstants.HTTP_STATUS_BAD_REQUEST);
            response.setContentType(ContentTypeConstants.TEXT_PLAIN);
            response.getWriter().write("文件过大");
            return;
        }
        
        // 设置响应头
        String fileName = file.getName();
        String contentType = getContentType(fileName);
        logger.info("文件MIME类型 - 文件名: {}, 内容类型: {}", fileName, contentType);
        
        response.setContentType(contentType);
        response.setContentLength((int) file.length());
        response.setHeader("Content-Disposition", StorageConstants.CONTENT_DISPOSITION_INLINE + "; filename=\"" + fileName + "\"");
        response.setHeader(StorageConstants.CACHE_CONTROL_HEADER, ContentTypeConstants.CACHE_CONTROL_PUBLIC);
        
        logger.info("设置响应头完成 - Content-Type: {}, Content-Length: {}, Content-Disposition: {}, Cache-Control: {}", 
                   contentType, file.length(), 
                   StorageConstants.CONTENT_DISPOSITION_INLINE + "; filename=\"" + fileName + "\"",
                   ContentTypeConstants.CACHE_CONTROL_PUBLIC);
        
        // 写入文件内容
        logger.info("开始写入文件内容 - 文件路径: {}", file.getAbsolutePath());
        try (InputStream inputStream = new FileInputStream(file)) {
            byte[] buffer = new byte[DesignFileConstants.BUFFER_SIZE];
            int totalBytesRead = 0;
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                response.getOutputStream().write(buffer, 0, bytesRead);
                totalBytesRead += bytesRead;
            }
            logger.info("文件内容写入完成 - 文件路径: {}, 总读取字节数: {}", file.getAbsolutePath(), totalBytesRead);
        }
    }

    /**
     * 根据文件扩展名获取MIME类型
     */
    private String getContentType(String fileName) {
        if (fileName == null) return ContentTypeConstants.APPLICATION_OCTET_STREAM;
        
        String extension = fileName.toLowerCase();
        
        // HTML相关
        if (extension.endsWith(".html") || extension.endsWith(".htm")) {
            return ContentTypeConstants.TEXT_HTML_UTF8;
        }
        
        // 样式表
        if (extension.endsWith(".css")) {
            return ContentTypeConstants.TEXT_CSS;
        }
        
        // JavaScript
        if (extension.endsWith(".js")) {
            return ContentTypeConstants.APPLICATION_JAVASCRIPT;
        }
        
        // 图像
        if (extension.endsWith(".png")) return ContentTypeConstants.IMAGE_PNG;
        if (extension.endsWith(".jpg") || extension.endsWith(".jpeg")) return ContentTypeConstants.IMAGE_JPEG;
        if (extension.endsWith(".gif")) return ContentTypeConstants.IMAGE_GIF;
        if (extension.endsWith(".svg")) return ContentTypeConstants.IMAGE_SVG;
        if (extension.endsWith(".ico")) return ContentTypeConstants.IMAGE_ICO;
        if (extension.endsWith(".bmp")) return ContentTypeConstants.IMAGE_BMP;
        if (extension.endsWith(".webp")) return ContentTypeConstants.IMAGE_WEBP;
        
        // 字体
        if (extension.endsWith(".woff")) return ContentTypeConstants.FONT_WOFF;
        if (extension.endsWith(".woff2")) return ContentTypeConstants.FONT_WOFF2;
        if (extension.endsWith(".ttf")) return ContentTypeConstants.FONT_TTF;
        if (extension.endsWith(".eot")) return ContentTypeConstants.FONT_EOT;
        if (extension.endsWith(".otf")) return ContentTypeConstants.FONT_TTF;
        
        // 文档
        if (extension.endsWith(".pdf")) return ContentTypeConstants.APPLICATION_PDF;
        if (extension.endsWith(".doc")) return ContentTypeConstants.APPLICATION_DOC;
        if (extension.endsWith(".docx")) return ContentTypeConstants.APPLICATION_DOCX;
        if (extension.endsWith(".xls")) return ContentTypeConstants.APPLICATION_XLS;
        if (extension.endsWith(".xlsx")) return ContentTypeConstants.APPLICATION_XLSX;
        if (extension.endsWith(".ppt")) return ContentTypeConstants.APPLICATION_PPT;
        if (extension.endsWith(".pptx")) return ContentTypeConstants.APPLICATION_PPTX;
        
        // 压缩包
        if (extension.endsWith(".zip")) return ContentTypeConstants.APPLICATION_ZIP;
        if (extension.endsWith(".rar")) return ContentTypeConstants.APPLICATION_RAR;
        if (extension.endsWith(".7z")) return ContentTypeConstants.APPLICATION_7Z;
        if (extension.endsWith(".tar")) return ContentTypeConstants.APPLICATION_TAR;
        if (extension.endsWith(".gz") || extension.endsWith(".gzip")) return ContentTypeConstants.APPLICATION_GZ;
        
        // 文本文件
        if (extension.endsWith(".txt")) return ContentTypeConstants.TEXT_PLAIN;
        if (extension.endsWith(".xml")) return ContentTypeConstants.TEXT_XML;
        if (extension.endsWith(".json")) return ContentTypeConstants.APPLICATION_JSON;
        if (extension.endsWith(".md")) return ContentTypeConstants.TEXT_PLAIN;
        
        return ContentTypeConstants.APPLICATION_OCTET_STREAM;
    }

    @Override
    public void serveFileSubPath(Long fileId, String subPath, HttpServletResponse response) throws Exception {
        logger.info("开始处理HTML压缩包子路径资源预览 - 文件ID: {}, 子路径: {}", fileId, subPath);
        
        DesignFile file = designFileRepository.getById(fileId);
        if (file == null) {
            logger.error("文件不存在 - 文件ID: {}", fileId);
            Exceptions.throwException(ApiPathConstants.ERROR_FILE_NOT_FOUND);
        }
        DesignFileVersion versionFile = designFileVersionRepository.getById(file.getCurrentVersionId());

        logger.info("文件信息 - ID: {}, 名称: {}, 类型: {}, 大小: {}", 
                   file.getId(), file.getName(), file.getFileType(), file.getFileSize());

        // 只支持HTML_ZIP类型的文件预览
        if (!FileTypeConstants.HTML_ZIP.equals(file.getFileType())) {
            logger.error("文件类型不支持预览 - 文件ID: {}, 文件类型: {}", fileId, file.getFileType());
            throw new IllegalArgumentException(ApiPathConstants.ERROR_FILE_TYPE_NOT_SUPPORTED);
        }

        // 清理子路径（去掉开头的斜杠）
        String cleanSubPath = subPath.startsWith("/") ? subPath.substring(1) : subPath;
        logger.info("清理后的子路径: {}", cleanSubPath);
        
        // 获取解压路径
        String extractPath = extractCacheManager.getExtractPath(fileId);
        logger.info("解压路径: {}", extractPath);
        
        // 使用锁防止并发解压
        ReentrantLock lock = extractCacheManager.getExtractLock(fileId);
        boolean acquired = lock.tryLock(DesignFileConstants.EXTRACT_LOCK_TIMEOUT, TimeUnit.SECONDS);
        
        if (!acquired) {
            logger.error("获取解压锁超时 - 文件ID: {}", fileId);
            throw new RuntimeException(ApiPathConstants.ERROR_FILE_PROCESSING);
        }
        
        try {
            logger.info("获取解压锁成功 - 文件ID: {}", fileId);
            
            // 使用统一解压方法处理文件
            boolean extractSuccess = unifiedExtractZipFile(versionFile, extractPath);
            if (!extractSuccess) {
                logger.error("文件解压失败 - 文件ID: {}, 解压路径: {}", fileId, extractPath);
                throw new RuntimeException("文件解压失败");
            }
            
            // 构造完整的文件路径
            String fullFilePath = Paths.get(extractPath, cleanSubPath).toString();
            logger.info("构造完整文件路径 - 文件ID: {}, 完整路径: {}", fileId, fullFilePath);
            
            // 提供静态文件服务
            logger.info("开始提供静态文件服务 - 文件ID: {}, 解压路径: {}, 完整路径: {}",
                    fileId, extractPath, fullFilePath);
            serveStaticFileWithSubPath(extractPath, fullFilePath, response);
            
            logger.info("静态文件服务完成 - 文件ID: {}", fileId);
        } finally {
            logger.info("释放解压锁 - 文件ID: {}", fileId);
            lock.unlock();
        }
    }

    /**
     * 上传文件到S3存储
     * @param file 要上传的文件
     * @return 上传是否成功
     */
    private String uploadToS3(MultipartFile file, DesignFile designFile) {
        String objectKey = designFile.getFileKey();
        logger.info("开始上传文件到S3 - 文件名: {}, 对象键: {}", file.getOriginalFilename(), objectKey);

        try {
            // 获取S3配置
            S3Config config = getS3ConfigForFile();
            
            if (config == null) {
                logger.warn("未找到S3配置，跳过上传 - 文件名: {}", file.getOriginalFilename());
                return null;
            }
            
            logger.info("使用S3配置上传文件 - 配置ID: {}, 端点: {}, 存储桶: {}", 
                       config.getId(), config.getEndpoint(), config.getBucket());
            
            // 上传文件到S3
            String ossKey = s3Service.uploadFile(config, file, objectKey);
            return ossKey;
        } catch (Exception e) {
            logger.error("上传文件到S3失败 - 文件名: {}, 对象键: {}, 错误信息: {}", 
                        file.getOriginalFilename(), objectKey, e.getMessage(), e);
            return null;
        }
    }

    @Override
    public DesignFileDTO uploadFileOnly(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            Exceptions.throwException(ApiPathConstants.ERROR_FILE_EMPTY);
        }

        try {
            // 检查文件大小
            if (file.getSize() > StorageConstants.MAX_FILE_SIZE_BYTES) {
                throw new IllegalArgumentException("文件大小超过限制: " + StorageConstants.MAX_FILE_SIZE_BYTES / (1024 * 1024) + "MB");
            }

            // 获取文件类型
            String fileType = getFileType(file.getOriginalFilename());
            logger.info("检测到文件类型为: {}，原始文件名为: {}", fileType, file.getOriginalFilename());
            
            // 对于HTML_ZIP、ZIP、RAR、TAR、GZ等压缩包类型，检查是否包含index.html
            if (FileTypeConstants.HTML_ZIP.equals(fileType) || 
                FileTypeConstants.ZIP.equals(fileType) ||
                FileTypeConstants.RAR.equals(fileType) ||
                FileTypeConstants.TAR.equals(fileType) ||
                FileTypeConstants.GZ.equals(fileType) ||
                FileTypeConstants.SEVEN_Z.equals(fileType)) {
                
                logger.info("检测到压缩包文件，检查是否包含index.html...");
                boolean hasIndexHtml = checkZipContainsIndexHtml(file);
                if (!hasIndexHtml) {
                    throw new IllegalArgumentException("压缩包文件必须包含index.html文件");
                }
                logger.info("压缩包文件检查通过，包含index.html");
                
                // 确保文件类型是HTML_ZIP
                fileType = FileTypeConstants.HTML_ZIP;
            }

            // 创建设计文件记录（仅包含基础信息）
            DesignFile designFile = new DesignFile();
            logger.info("上传文件的原始名称为:{}",file.getOriginalFilename());
            designFile.setName(file.getOriginalFilename());
            designFile.setFileType(fileType);
            designFile.setFileSize(file.getSize());
            designFile.setVersion(DesignFileConstants.DEFAULT_VERSION);
            designFile.setStatus(DesignFileConstants.STATUS_NORMAL);
            designFile.setUsedStatus(DesignFileConstants.USED_STATUS_UNUSED);
            designFile.setSecurityStatus(DesignFileConstants.SECURITY_STATUS_UNSCANNED);

            designFile.setStorageStatus(DesignFileConstants.STORAGE_STATUS_UPLOADING);
            
            LocalDateTime currentTime = LocalDateTime.now();
            designFile.setCreateTime(currentTime);
            designFile.setUpdateTime(currentTime);

            logger.info("准备保存设计文件记录到数据库 - 文件名: {}, 文件大小: {}", designFile.getName(), designFile.getFileSize());
            logger.info("准备保存设计文件记录到数据库 - 文件数据为: {}", designFile);

            // 保存文件信息到数据库
            designFileRepository.save(designFile);
            
            logger.info("设计文件记录保存成功 - 文件ID: {}", designFile.getId());

            // 尝试上传文件到S3存储
            String ossKey = "";
            try {
                ossKey = uploadToS3(file, designFile);
            } catch (Exception e) {
                logger.error("上传文件到S3失败 - 文件名: {}, 对象键: {}, 错误信息: {}", 
                           file.getOriginalFilename(), designFile.getFileKey(), e.getMessage());
            }

            if (StringUtils.isNotBlank(ossKey)) {
                designFile.setStorageStatus(DesignFileConstants.STORAGE_STATUS_STORED);
            } else {
                designFile.setStorageStatus(DesignFileConstants.STORAGE_STATUS_FAILED);
            }

            logger.info("准备更新设计文件记录 - 文件ID: {}, 存储状态: {}", designFile.getId(), designFile.getStorageStatus());

            designFile.setFileKey(ossKey);
            // 更新文件存储状态
            designFileRepository.updateById(designFile);
            
            logger.info("设计文件记录更新成功 - 文件ID: {}", designFile.getId());
            
            // 创建初始版本记录
            DesignFileVersion versionFile = createInitialVersion(designFile);

            // 如果是HTML_ZIP类型的文件，进行预解压（在上传S3之前）
            if (FileTypeConstants.HTML_ZIP.equals(designFile.getFileType())) {
                try {
                    String extractPath = extractCacheManager.getExtractPath(designFile.getId());
                    logger.info("在上传S3之前进行解压 - 文件ID: {}, 解压路径: {}", designFile.getId(), extractPath);
                    // 使用上传的原始文件进行解压
                    boolean extractSuccess = unifiedExtractZipFile(versionFile, extractPath);
                    if (extractSuccess) {
                        logger.info("HTML ZIP文件预解压完成 - 文件ID: {}, 解压路径: {}", designFile.getId(), extractPath);
                    } else {
                        logger.error("HTML ZIP文件预解压失败 - 文件ID: {}, 解压路径: {}", designFile.getId(), extractPath);
                    }
                } catch (Exception e) {
                    logger.error("HTML ZIP文件预解压失败 - 文件ID: {}", designFile.getId(), e);
                }
            }

            return convertToDTO(designFile);
        } catch (Exception e) {
            // 仅当初始文件保存失败时才抛出异常
            logger.error("设计文件上传过程中发生严重错误", e);
            throw new RuntimeException(ApiPathConstants.ERROR_FILE_UPLOAD_FAILED + ": " + e.getMessage(), e);
        }
    }

    /**
     * 检查ZIP文件是否包含index.html文件
     * @param file MultipartFile对象
     * @return 是否包含index.html
     */
    private boolean checkZipContainsIndexHtml(MultipartFile file) {
        try {
            // 检查文件扩展名
            String fileName = file.getOriginalFilename().toLowerCase();
            if (fileName.endsWith(".zip")) {
                return checkZipFileContainsIndexHtml(file);
            } else if (fileName.endsWith(".rar")) {
                return checkRarFileContainsIndexHtml(file);
            } else if (fileName.endsWith(".7z")) {
                return check7zFileContainsIndexHtml(file);
            } else if (fileName.endsWith(".tar") || fileName.endsWith(".gz") || fileName.endsWith(".tgz")) {
                return checkTarFileContainsIndexHtml(file);
            } else {
                // 对于其他类型的压缩文件，默认返回true（不强制检查）
                logger.warn("不支持的压缩文件类型: {}", fileName);
                return true;
            }
        } catch (Exception e) {
            logger.error("检查压缩文件是否包含index.html时发生错误: {}", e.getMessage(), e);
            // 出现异常时，默认返回false，阻止上传
            return false;
        }
    }

    /**
     * 检查ZIP文件是否包含index.html文件，并验证只有一个index.html
     * @param file MultipartFile对象
     * @return 是否包含且仅包含一个index.html
     */
    private boolean checkZipFileContainsIndexHtml(MultipartFile file) {
        try (java.util.zip.ZipInputStream zipInputStream = new java.util.zip.ZipInputStream(file.getInputStream())) {
            java.util.List<String> indexHtmlPaths = new java.util.ArrayList<>(); // 记录所有index.html的路径
            java.util.zip.ZipEntry entry;
            while ((entry = zipInputStream.getNextEntry()) != null) {
                String entryName = entry.getName().toLowerCase();
                // 检查是否包含index.html文件（忽略路径）
                if (entryName.endsWith("index.html")) {
                    indexHtmlPaths.add(entry.getName()); // 保存原始路径
                }
                zipInputStream.closeEntry();
            }
            
            // 检查index.html的数量
            if (indexHtmlPaths.isEmpty()) {
                logger.error("ZIP文件不包含index.html文件");
                return false;
            } else if (indexHtmlPaths.size() > 1) {
                logger.error("ZIP文件包含多个index.html文件: {}", String.join(", ", indexHtmlPaths));
                return false;
            }
            
            // 获取index.html所在的目录路径
            String indexHtmlPath = indexHtmlPaths.get(0);
            String indexHtmlDir = getIndexHtmlDirectory(indexHtmlPath);
            logger.info("index.html路径: {}, index.html所在目录: {}", indexHtmlPath, indexHtmlDir);
            
            return true;
        } catch (Exception e) {
            logger.error("检查ZIP文件是否包含index.html时发生错误: {}", e.getMessage(), e);
            return false;
        }
    }

    /**
     * 获取index.html所在目录路径
     * @param indexPath index.html的完整路径
     * @return index.html所在目录路径
     */
    private String getIndexHtmlDirectory(String indexPath) {
        int lastSlash = indexPath.lastIndexOf('/');
        if (lastSlash >= 0) {
            return indexPath.substring(0, lastSlash + 1); // 包含/
        }
        return ""; // 根目录
    }

    /**
     * 检查RAR文件是否包含index.html文件（简化实现，实际项目中可能需要使用专门的RAR库）
     * @param file MultipartFile对象
     * @return 是否包含index.html
     */
    private boolean checkRarFileContainsIndexHtml(MultipartFile file) {
        // 简化实现，返回true（实际项目中需要使用专门的RAR库）
        logger.warn("RAR文件检查功能未实现，跳过检查");
        return true;
    }

    /**
     * 检查7Z文件是否包含index.html文件（简化实现，实际项目中可能需要使用专门的7Z库）
     * @param file MultipartFile对象
     * @return 是否包含index.html
     */
    private boolean check7zFileContainsIndexHtml(MultipartFile file) {
        // 简化实现，返回true（实际项目中需要使用专门的7Z库）
        logger.warn("7Z文件检查功能未实现，跳过检查");
        return true;
    }

    /**
     * 检查TAR文件是否包含index.html文件（简化实现）
     * @param file MultipartFile对象
     * @return 是否包含index.html
     */
    private boolean checkTarFileContainsIndexHtml(MultipartFile file) {
        // 简化实现，返回true（实际项目中需要使用专门的TAR库）
        logger.warn("TAR文件检查功能未实现，跳过检查");
        return true;
    }

    @Override
    public void cleanupExpiredExtractFiles(long maxAge) {
        extractCacheManager.cleanupExpiredFiles(maxAge > 0 ? maxAge : DesignFileConstants.CACHE_MAX_AGE);
    }

    @Override
    public java.util.List<DesignFileVersionDTO> getVersionsByFileId(Long fileId) {
        java.util.List<DesignFileVersion> versions = designFileVersionRepository.getVersionsByFileId(fileId);
        return versions.stream()
            .map(this::convertToDTO)
            .collect(java.util.stream.Collectors.toList());
    }

    @Override
    public DesignFileVersionDTO getVersionByFileIdAndVersionNumber(Long fileId, String versionNumber) {
        DesignFileVersion version = designFileVersionRepository.getVersionByFileIdAndVersionNumber(fileId, versionNumber);
        return version != null ? convertToDTO(version) : null;
    }

    @Override
    public DesignFileVersionDTO getVersionById(Long versionId) {
        // 优化版本：直接通过版本ID获取，不需要通过文件ID和版本号
        DesignFileVersion version = designFileVersionRepository.getById(versionId);
        return version != null ? convertToDTO(version) : null;
    }

    @Override
    public Long getFileIdByVersionId(Long versionId) {
        DesignFileVersion version = designFileVersionRepository.getById(versionId);
        return version != null ? version.getFileId() : null;
    }

    @Override
    public Long getFileIdByCurrentVersionId(Long currentVersionId) {
        // 通过currentVersionId获取对应的文件ID
        DesignFileVersion version = designFileVersionRepository.getById(currentVersionId);
        return version != null ? version.getFileId() : null;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public DesignFileVersionDTO createVersion(Long fileId, DesignFileVersionDTO versionDTO) {
        // 获取原始文件信息
        DesignFile originalFile = designFileRepository.getById(fileId);
        if (originalFile == null) {
            Exceptions.throwException("文件不存在");
        }

        // 获取S3配置
        S3Config config = s3ConfigService.getByBusinessType(StorageConstants.BUSINESS_TYPE_DESIGN_FILE);

        // 生成版本化的文件键名
        String versionedFileKey = generateVersionedFileKey(originalFile, versionDTO.getVersionNumber());

        // 复制当前文件到版本化路径
        try {
            InputStream fileContent = s3Service.downloadFile(config, versionDTO.getFileKey());
            String ossKey = s3Service.uploadFile(config, fileContent, versionedFileKey, getContentType(originalFile.getFileType()));

            logger.info("手动创建版本 - 文件复制成功 - 从: {}, 到: {}", originalFile.getFileKey(), versionedFileKey);
        } catch (Exception e) {
            logger.error("手动创建版本 - 复制文件到版本化路径失败 - 源文件: {}, 目标文件: {}", originalFile.getFileKey(), versionedFileKey, e);
            // 如果复制失败，使用原始文件键名
            versionedFileKey = originalFile.getFileKey();
        }

        // 创建版本记录
        DesignFileVersion version = new DesignFileVersion();
        version.setFileId(fileId);
        version.setVersionNumber(versionDTO.getVersionNumber());
        version.setDescription(versionDTO.getDescription());
        version.setFileKey(versionedFileKey); // 使用版本化的文件键名
        version.setFileSize(originalFile.getFileSize());
        version.setFileType(originalFile.getFileType());
        version.setFileMd5(originalFile.getFileMd5());
        version.setStatus(originalFile.getStatus());
        version.setVersionStatus("ACTIVE");
        version.setOperationType("CREATE");
        version.setOperatorId(LoginSessionTool.getCurrentUser().getUserId()); // 获取当前用户ID
        // 设置新增字段
        version.setSecurityStatus(originalFile.getSecurityStatus());
        version.setLastAccessTime(originalFile.getLastAccessTime());
        version.setStorageStatus(originalFile.getStorageStatus());
        version.setCreatedBy(originalFile.getCreatedBy());
        version.setUpdatedBy(LoginSessionTool.getCurrentUser().getUserId());
        LocalDateTime currentTime = LocalDateTime.now();
        version.setCreateTime(currentTime);
        version.setUpdateTime(currentTime);

        // 保存版本记录
        designFileVersionRepository.save(version);

        // 更新文件的currentVersionId字段，指向最新版本
        originalFile.setCurrentVersionId(version.getId());
        originalFile.setVersion(version.getVersionNumber());
        originalFile.setUpdateTime(currentTime);
        designFileRepository.updateById(originalFile);

        logger.info("创建新版本并更新currentVersionId - 文件ID: {}, 版本ID: {}, 版本号: {}", 
                   fileId, version.getId(), version.getVersionNumber());

        return convertToDTO(version);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public DesignFileVersionDTO updateVersion(Long versionId, DesignFileVersionDTO versionDTO) {
        DesignFileVersion version = designFileVersionRepository.getById(versionId);
        if (version == null) {
            Exceptions.throwException("版本不存在");
        }

        // 更新版本信息
        version.setDescription(versionDTO.getDescription());
        version.setVersionStatus(versionDTO.getVersionStatus());
        version.setUpdateTime(LocalDateTime.now());

        designFileVersionRepository.updateById(version);

        return convertToDTO(version);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteVersion(Long versionId) {
        DesignFileVersion version = designFileVersionRepository.getById(versionId);
        if (version == null) {
            Exceptions.throwException("版本不存在");
        }

        // 删除版本记录
        designFileVersionRepository.removeById(versionId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public DesignFileDTO restoreToVersion(Long fileId, String versionNumber) {
        // 获取指定版本
        DesignFileVersion version = designFileVersionRepository.getVersionByFileIdAndVersionNumber(fileId, versionNumber);
        if (version == null) {
            Exceptions.throwException("版本不存在");
        }

        // 获取原始文件
        DesignFile originalFile = designFileRepository.getById(fileId);
        if (originalFile == null) {
            Exceptions.throwException("文件不存在");
        }

        // 版本回退策略：从当前文件位置获取内容，恢复为指定版本的状态
        String currentFileKey = originalFile.getFileKey();

        logger.info("开始版本回退 - 文件ID: {}, 当前文件键: {}, 目标版本: {}",
                   fileId, currentFileKey, versionNumber);

        originalFile.setFileKey(version.getFileKey());
        originalFile.setFileSize(version.getFileSize());
        originalFile.setFileType(version.getFileType());
        originalFile.setFileMd5(version.getFileMd5());
        originalFile.setVersion(version.getVersionNumber());
        originalFile.setStatus(version.getStatus());
        originalFile.setUpdateTime(LocalDateTime.now());

        // 更新currentVersionId为恢复到的版本ID
        originalFile.setCurrentVersionId(version.getId());

        // 更新文件记录
        designFileRepository.updateById(originalFile);

        // 创建恢复操作的版本记录
        DesignFileVersion restoreVersion = new DesignFileVersion();
        restoreVersion.setFileId(fileId);
        restoreVersion.setVersionNumber(incrementVersionNumber(version.getVersionNumber()) + "-restore");
        restoreVersion.setDescription("恢复到版本 " + versionNumber);
        restoreVersion.setFileKey(currentFileKey);
        restoreVersion.setFileSize(version.getFileSize());
        restoreVersion.setFileType(version.getFileType());
        restoreVersion.setFileMd5(version.getFileMd5());
        restoreVersion.setStatus(version.getStatus());
        restoreVersion.setVersionStatus("ACTIVE");
        restoreVersion.setOperationType("RESTORE");
        restoreVersion.setOperatorId(LoginSessionTool.getCurrentUser().getUserId()); // 获取当前用户ID
        LocalDateTime currentTime = LocalDateTime.now();
        restoreVersion.setCreateTime(currentTime);
        restoreVersion.setUpdateTime(currentTime);

        // 保存恢复操作的版本记录
        designFileVersionRepository.save(restoreVersion);

        // 再次更新currentVersionId为恢复操作版本的ID
        originalFile.setCurrentVersionId(restoreVersion.getId());
        designFileRepository.updateById(originalFile);

        logger.info("版本回退成功并更新currentVersionId - 文件ID: {}, 恢复到版本: {}, 最新版本ID: {}",
                   fileId, versionNumber, restoreVersion.getId());

        return convertToDTO(originalFile);
    }

    /**
     * 创建初始版本记录
     * @param designFile 设计文件
     */
    private DesignFileVersion createInitialVersion(DesignFile designFile) {
        DesignFileVersion initialVersion = new DesignFileVersion();
        // 创建初始版本记录
        initialVersion.setFileId(designFile.getId());
        initialVersion.setVersionNumber("v1.0");
        initialVersion.setDescription("初始版本");
        initialVersion.setFileKey(designFile.getFileKey()); // 使用版本化的文件键名
        initialVersion.setFileSize(designFile.getFileSize());
        initialVersion.setFileType(designFile.getFileType());
        initialVersion.setFileMd5(designFile.getFileMd5());
        initialVersion.setStatus(designFile.getStatus());
        initialVersion.setVersionStatus("ACTIVE");
        initialVersion.setOperationType("CREATE");
        initialVersion.setOperatorId(designFile.getOwnerId() != null ? designFile.getOwnerId() : LoginSessionTool.getCurrentUser().getUserId()); // 使用文件所有者ID或当前用户ID作为操作者
        // 设置新增字段
        initialVersion.setSecurityStatus(designFile.getSecurityStatus());
        initialVersion.setLastAccessTime(designFile.getLastAccessTime());
        initialVersion.setStorageStatus(designFile.getStorageStatus());
        initialVersion.setCreatedBy(designFile.getCreatedBy());
        initialVersion.setUpdatedBy(LoginSessionTool.getCurrentUser().getUserId());
        LocalDateTime currentTime = LocalDateTime.now();
        initialVersion.setCreateTime(currentTime);
        initialVersion.setUpdateTime(currentTime);

        // 保存初始版本记录
        designFileVersionRepository.save(initialVersion);

        // 更新文件的currentVersionId字段，指向初始版本
        designFile.setCurrentVersionId(initialVersion.getId());
        designFile.setUpdateTime(currentTime);
        designFileRepository.updateById(designFile);

        logger.info("初始版本记录创建成功并更新currentVersionId - 文件ID: {}, 版本ID: {}, 版本号: {}",
                   designFile.getId(), initialVersion.getId(), initialVersion.getVersionNumber());
        return initialVersion;
    }

    /**
     * 创建更新版本记录
     * @param designFile 设计文件
     */
    private void createUpdateVersion(DesignFile designFile) {
        try {
            // 获取最新的版本号
            String latestVersionNumber;
            List<DesignFileVersion> versions = designFileVersionRepository.getVersionsByFileId(designFile.getId());
            if (!versions.isEmpty()) {
                // 获取最新的版本号并递增
                DesignFileVersion latestVersion = versions.get(0);
                latestVersionNumber = incrementVersionNumber(latestVersion.getVersionNumber());
            } else {
                latestVersionNumber = "v1.0";
            }

            // 获取S3配置
            S3Config config = s3ConfigService.getByBusinessType(StorageConstants.BUSINESS_TYPE_DESIGN_FILE);
            
            // 生成版本化的文件键名
            String versionedFileKey = generateVersionedFileKey(designFile, latestVersionNumber);
            
            // 复制当前文件到版本化路径
            try {
                InputStream fileContent = s3Service.downloadFile(config, designFile.getFileKey());
                s3Service.uploadFile(config, fileContent, versionedFileKey, getContentType(designFile.getFileType()));
                
                logger.info("文件复制成功 - 从: {}, 到: {}", designFile.getFileKey(), versionedFileKey);
            } catch (Exception e) {
                logger.error("复制文件到版本化路径失败 - 源文件: {}, 目标文件: {}", designFile.getFileKey(), versionedFileKey, e);
                // 如果复制失败，使用原始文件键名
                versionedFileKey = designFile.getFileKey();
            }

            // 创建更新版本记录
            DesignFileVersion updateVersion = new DesignFileVersion();
            updateVersion.setFileId(designFile.getId());
            updateVersion.setVersionNumber(latestVersionNumber);
            updateVersion.setDescription("更新版本");
            updateVersion.setFileKey(versionedFileKey); // 使用版本化的文件键名
            updateVersion.setFileSize(designFile.getFileSize());
            updateVersion.setFileType(designFile.getFileType());
            updateVersion.setFileMd5(designFile.getFileMd5());
            updateVersion.setStatus(designFile.getStatus());
            updateVersion.setVersionStatus("ACTIVE");
            updateVersion.setOperationType("UPDATE");
            updateVersion.setOperatorId(designFile.getOwnerId() != null ? designFile.getOwnerId() : LoginSessionTool.getCurrentUser().getUserId()); // 使用文件所有者ID或当前用户ID作为操作者
            // 设置新增字段
            updateVersion.setSecurityStatus(designFile.getSecurityStatus());
            updateVersion.setLastAccessTime(designFile.getLastAccessTime());
            updateVersion.setStorageStatus(designFile.getStorageStatus());
            updateVersion.setCreatedBy(designFile.getCreatedBy());
            updateVersion.setUpdatedBy(LoginSessionTool.getCurrentUser().getUserId());
            LocalDateTime currentTime = LocalDateTime.now();
            updateVersion.setCreateTime(currentTime);
            updateVersion.setUpdateTime(currentTime);

            // 保存更新版本记录
            designFileVersionRepository.save(updateVersion);
            
            logger.info("更新版本记录创建成功 - 文件ID: {}, 版本号: {}", designFile.getId(), updateVersion.getVersionNumber());
        } catch (Exception e) {
            logger.error("创建更新版本记录失败 - 文件ID: {}", designFile.getId(), e);
            // 这里不抛出异常，因为版本记录创建失败不影响文件更新的主要功能
        }
    }

    /**
     * 递增版本号
     * @param versionNumber 当前版本号
     * @return 递增后的版本号
     */
    private String incrementVersionNumber(String versionNumber) {
        try {
            if (versionNumber == null || versionNumber.isEmpty()) {
                return "v1.0";
            }
            
            // 移除前缀v（如果有）
            String cleanVersion = versionNumber.startsWith("v") ? versionNumber.substring(1) : versionNumber;
            
            // 分割版本号
            String[] parts = cleanVersion.split("\\.");
            if (parts.length >= 2) {
                int major = Integer.parseInt(parts[0]);
                int minor = Integer.parseInt(parts[1]);
                // 递增次要版本号
                return "v" + major + "." + (minor + 1);
            } else {
                // 如果格式不正确，默认返回v1.0
                return "v1.0";
            }
        } catch (Exception e) {
            logger.error("递增版本号失败，使用默认版本号: {}", versionNumber, e);
            return "v1.0";
        }
    }

    @Override
    public DesignFileVersionDTO convertToDTO(DesignFileVersion entity) {
        if (entity == null) {
            return null;
        }

        DesignFileVersionDTO dto = new DesignFileVersionDTO();
        dto.setId(entity.getId());
        dto.setFileId(entity.getFileId());
        dto.setVersionNumber(entity.getVersionNumber());
        dto.setDescription(entity.getDescription());
        dto.setFileKey(entity.getFileKey());
        dto.setFileSize(entity.getFileSize());
        dto.setFileType(entity.getFileType());
        dto.setFileMd5(entity.getFileMd5());
        dto.setStatus(entity.getStatus());
        dto.setVersionStatus(entity.getVersionStatus());
        dto.setOperationType(entity.getOperationType());
        dto.setOperatorId(entity.getOperatorId());

        return dto;
    }

    @Override
    public DesignFileVersionPreviewDTO generateVersionPreviewData(Long versionId) {
        // 获取版本数据
        DesignFileVersionDTO version = getVersionById(versionId);
        if (version == null) {
            return null;
        }

        // 生成版本特定的预览URL
        String previewUrl = generateVersionPreviewUrl(versionId, version);

        // 创建版本预览DTO
        DesignFileVersionPreviewDTO previewDTO = new DesignFileVersionPreviewDTO();
        
        // 复制版本数据
        previewDTO.setId(version.getId());
        previewDTO.setFileId(version.getFileId());
        previewDTO.setVersionNumber(version.getVersionNumber());
        previewDTO.setDescription(version.getDescription());
        previewDTO.setFileKey(version.getFileKey());
        previewDTO.setFileSize(version.getFileSize());
        previewDTO.setFileType(version.getFileType());
        previewDTO.setFileMd5(version.getFileMd5());
        previewDTO.setStatus(version.getStatus());
        previewDTO.setVersionStatus(version.getVersionStatus());
        previewDTO.setOperationType(version.getOperationType());
        previewDTO.setOperatorId(version.getOperatorId());
        previewDTO.setCreateTime(version.getCreateTime());
        previewDTO.setUpdateTime(version.getUpdateTime());
        
        // 设置预览信息
        previewDTO.setPreviewUrl(previewUrl);
        previewDTO.setPreviewType(getPreviewType(version.getFileType()));
        previewDTO.setCanPreview(previewDTO.getPreviewType() != null && !previewDTO.getPreviewType().equals("download"));

        return previewDTO;
    }

    /**
     * 生成版本特定的预览URL
     */
    private String generateVersionPreviewUrl(Long versionId, DesignFileVersionDTO version) {
        // 基于版本ID和安全令牌生成预览URL
        // 这里我们生成一个简单的预览URL，实际的访问会通过SecurePreviewController处理
        String baseUrl = "/secure-preview/version/" + versionId + "/preview";
        return baseUrl;
    }

    @Override
    public void serveVersionSubPath(Long versionId, String subPath, HttpServletResponse response) throws Exception {
        logger.info("开始处理版本HTML压缩包子路径资源预览 - 版本ID: {}, 子路径: {}", versionId, subPath);
        
        // 获取版本数据
        DesignFileVersion versionFile = designFileVersionRepository.getById(versionId);
        if (versionFile == null) {
            logger.error("版本不存在 - 版本ID: {}", versionId);
            Exceptions.throwException("版本不存在");
        }

        // 获取文件信息
        DesignFile file = designFileRepository.getById(versionFile.getFileId());
        if (file == null) {
            logger.error("文件不存在 - 文件ID: {}, 版本ID: {}", versionFile.getFileId(), versionId);
            Exceptions.throwException(ApiPathConstants.ERROR_FILE_NOT_FOUND);
        }

        logger.info("版本信息 - 版本ID: {}, 文件ID: {}, 名称: {}, 类型: {}, 文件key: {}", 
                   versionId, file.getId(), file.getName(), versionFile.getFileType(), versionFile.getFileKey());

        // 只支持HTML_ZIP类型的文件预览
        if (!FileTypeConstants.HTML_ZIP.equals(versionFile.getFileType())) {
            logger.error("文件类型不支持预览 - 版本ID: {}, 文件类型: {}", versionId, versionFile.getFileType());
            throw new IllegalArgumentException(ApiPathConstants.ERROR_FILE_TYPE_NOT_SUPPORTED);
        }

        // 清理子路径（去掉开头的斜杠）
        String cleanSubPath = subPath.startsWith("/") ? subPath.substring(1) : subPath;
        logger.info("清理后的子路径: {}", cleanSubPath);
        
        // 获取解压路径（使用版本ID）
        String extractPath = extractCacheManager.getExtractPath(versionId);
        logger.info("解压路径: {}", extractPath);
        
        // 使用锁防止并发解压（使用版本特定的锁）
        ReentrantLock lock = extractCacheManager.getExtractLock(versionId);
        boolean acquired = lock.tryLock(DesignFileConstants.EXTRACT_LOCK_TIMEOUT, TimeUnit.SECONDS);
        
        if (!acquired) {
            logger.error("获取解压锁超时 - 版本ID: {}", versionId);
            throw new RuntimeException(ApiPathConstants.ERROR_FILE_PROCESSING);
        }
        
        try {
            logger.info("获取解压锁成功 - 版本ID: {}", versionId);
            
            // 使用统一解压方法处理文件（使用版本的文件key）
            boolean extractSuccess = unifiedExtractZipFile(versionFile, extractPath);
            if (!extractSuccess) {
                logger.error("版本文件解压失败 - 版本ID: {}, 解压路径: {}", versionId, extractPath);
                throw new RuntimeException("版本文件解压失败");
            }
            
            // 构造完整的文件路径
            String fullFilePath = Paths.get(extractPath, cleanSubPath).toString();
            logger.info("构造完整文件路径 - 版本ID: {}, 完整路径: {}", versionId, fullFilePath);
            
            // 提供静态文件服务
            serveStaticFileWithSubPath(extractPath, fullFilePath, response);
            
            logger.info("版本静态文件服务完成 - 版本ID: {}", versionId);
        } finally {
            logger.info("释放解压锁 - 版本ID: {}", versionId);
            lock.unlock();
        }
    }
    
    /**
     * 生成版本化的文件键名
     * @param designFile 设计文件
     * @param versionNumber 版本号
     * @return 版本化的文件键名
     */
    private String generateVersionedFileKey(DesignFile designFile, String versionNumber) {
        // 文件存储路径格式：design-files/{projectId}/{fileId}/v{versionNumber}/{originalFileName}
        String projectId = designFile.getProjectId() != null ? designFile.getProjectId().toString() : "unassigned";
        String fileId = designFile.getId().toString();
        
        // 获取原始文件名（从fileKey中提取）
        String originalFileName = extractFileName(designFile.getFileKey());
        
        // 生成版本化路径
        return String.format("design-files/%s/%s/%s/%s", projectId, fileId, versionNumber, originalFileName);
    }
    
    /**
     * 从文件键名中提取原始文件名
     * @param fileKey 文件键名
     * @return 原始文件名
     */
    private String extractFileName(String fileKey) {
        int lastSlash = fileKey.lastIndexOf('/');
        if (lastSlash >= 0) {
            return fileKey.substring(lastSlash + 1);
        }
        return fileKey;
    }

    /**
     * 根据版本ID获取文件下载URL（支持多版本下载）
     * 
     * @param versionId 版本ID
     * @return 下载URL
     */
    @Override
    public String getVersionDownloadUrl(Long versionId) {
        if (versionId == null) {
            throw new IllegalArgumentException("版本ID不能为空");
        }

        // 首先通过版本ID获取文件ID
        Long fileId = getFileIdByCurrentVersionId(versionId);
        if (fileId == null) {
            // 尝试直接通过版本ID获取文件ID
            fileId = getFileIdByVersionId(versionId);
            if (fileId == null) {
                throw new IllegalArgumentException("版本ID不存在: " + versionId);
            }
        }

        // 生成基于版本ID的下载URL
        String downloadUrl = ApiPathConstants.PREVIEW_BASE_URL + 
                           ApiPathConstants.DESIGN_FILE_PATH + "/download/" + fileId + "/version/" + versionId;
        
        logger.info("生成版本下载URL - 版本ID: {}, 文件ID: {}, 下载URL: {}", versionId, fileId, downloadUrl);
        return downloadUrl;
    }
}
